﻿using System.Collections.Generic;
using ClassLibrary;
using ClassLibrary.Agent;
using ClassLibrary.Communication;
using ClassLibrary.Configuration;
using ClassLibrary.Configuration.Switch;
using ClassLibrary.SignalType.SynchronousTransportModuleLevel1;
using System;
using ClassLibrary.SignalType.SynchronousTransportModuleLevel1.VirtualContainer;
using ClassLibrary.Synchronization;

namespace NetworkNode.Switching
{
    /// <summary>
    /// @Author Marcin Król
    /// SDH Switch
    /// </summary>
    class Switch
    {
        // --- environment variables
        private readonly CommunicationService _communicationService = CommunicationService.GetInstance();
        private readonly Clock _clock = Clock.GetInstance();
        // ---

        // --- axuliary variables
        private readonly int _numberOfPhysicalPorts;
        private readonly int _numberOfLogicalPorts;
        private readonly int _numberOfMultiplexers;

        private readonly String _id;

        private readonly SwitchingTable _switchingTable;
        private readonly Dictionary<int, Stm1> _egressStm1Dictionary = new Dictionary<int, Stm1>();
        private readonly Port[] _physicalPorts;

        //[logicalPortId]
        private readonly Dictionary<int, Multiplexer> _multiplexers = new Dictionary<int, Multiplexer>();

        private readonly Agent _agent;
        private readonly ConnectionControler_node _connectionController;

        /// <summary>
        /// initializes the object, loads configuration
        /// </summary>
        /// <param name="configFilePath">path to the configuration file</param>
        public Switch(String configFilePath)
        {
            //load configuration
            var switchConfiguration = (SwitchConfig)Configuration.GetConfiguration(configFilePath, typeof(SwitchConfig));

            //reading some variables
            _id = switchConfiguration.Id;
            _numberOfPhysicalPorts = switchConfiguration.PhysicalPortsNo;
            _numberOfLogicalPorts = switchConfiguration.LogicalPortsNo;
            _numberOfMultiplexers = switchConfiguration.MultiplexerInfos.Length;

            //set title
            Console.Title = _id;

            //settin up connection service
            _communicationService.Connect(switchConfiguration.WireCloudIp, switchConfiguration.WireCloudPort);
            _communicationService.Login(_id);
            _physicalPorts = _communicationService.SetupPorts(_numberOfPhysicalPorts);

            //settin up synchronization service
            _clock.Setup(switchConfiguration.ClockDuration);
            _clock.Start();

            //settin up managing service
            _agent = new Agent(switchConfiguration.ManagerIp, switchConfiguration.ManagerPort, _id);
            _connectionController = new ConnectionControler_node(switchConfiguration.ControlIp, switchConfiguration.ControlPort, _id);

            //loading switching table
            _switchingTable = new SwitchingTable(switchConfiguration.ConfSwitchingTable, _numberOfLogicalPorts);

            //setting up multiplexers
            for (var i = 0; i < _numberOfMultiplexers; i++)
            {
                var multiplexerInfo = switchConfiguration.MultiplexerInfos[i];
                var egressLogicalPortId = multiplexerInfo.EgressLogicalPortId;
                var ingressPortsNo = multiplexerInfo.IngressPortsNo;
                var firstIngressPhysicalPortId = multiplexerInfo.FirstIngressPhysicalPortId;

                var ports = new Port[ingressPortsNo];
                for (var j = 0; j < ingressPortsNo; j++)
                {
                    ports[j] = _physicalPorts[j + firstIngressPhysicalPortId];
                }
                _multiplexers[egressLogicalPortId] = new Multiplexer(ports, i, egressLogicalPortId, firstIngressPhysicalPortId);
            }

            //setting up events handling
            var phyPortsNotUsedByMultiplexersNo = _numberOfLogicalPorts - _numberOfMultiplexers;
            for (var i = 0; i < phyPortsNotUsedByMultiplexersNo; i++)
            {
                _physicalPorts[i].DataReceived += HandleEvents;
            }
            _clock.Tick += HandleEvents;
            _agent.ConfigReceived += HandleEvents;//todo
            _connectionController.ConfigReceived += HandleEvents;//todo
        }

        /// <summary>
        /// handles all events
        /// </summary>
        /// <param name="associate">object associated with event invokation</param>
        /// <param name="eventType">type of invoked event</param>
        void HandleEvents(object associate, EventType eventType)
        {
            switch (eventType)
            {
                case EventType.DataReceived:
                    HandleDataReceived(associate);
                    break;
                case EventType.ConfigReceived:
                    HandleConfigReceived(associate);
                    break;
                case EventType.ClockTick:
                    HandleClockTick();
                    break;
            }
        }


        /// <summary>
        /// handles the event when switch receives data
        /// </summary>
        /// <param name="sender"></param>
        void HandleDataReceived(object sender)
        {
            //handle data received on Port
            if (sender is Port)
            {
                var port = (Port)sender;
                var dataReceived = port.Read();
                if (dataReceived is Stm1)
                {
                    // --- PrintsOut
                    if (Utils.Test)
                    {
                        Console.WriteLine("@STM1 Received<========================");
                        Console.WriteLine(dataReceived.ToString());
                    }
                    // ---
                    OnStm1PacketReceived((Stm1)dataReceived, port.Id);
                }
                else
                {
                    throw new Exception("data other than STM1 received on STM1 Port");
                }
            }

        }

        /// <summary>
        /// handles configuration queries
        /// </summary>
        /// <param name="config"></param>
        void HandleConfigReceived(object config)
        {
            var container = config as Query;
            if (container != null)
            {
                // --- PrintsOut
                if (Utils.Test)
                {
                    Console.WriteLine("@Config Received<========================");
                    Console.WriteLine(container.ToString());
                }
                // ---

                var ingressSwitchingInfo = new SwitchingInfo(container.Record.Ingress.PortId, container.Record.Ingress.VcId);
                switch (container.QueryType)
                {
                    case QueryType.Add:
                        _switchingTable.AddRecord(ingressSwitchingInfo, new SwitchingInfo(container.Record.Egress.PortId, container.Record.Egress.VcId));
                        break;
                    case QueryType.Delete:
                        _switchingTable.DeleteRecord(ingressSwitchingInfo);
                        break;
                    case QueryType.Update:
                        _switchingTable.UpdateRecord(ingressSwitchingInfo, new SwitchingInfo(container.Record.Egress.PortId, container.Record.Egress.VcId));
                        break;
                    case QueryType.SwitchingTableRequest:
                        _agent.Send(_switchingTable.GetSwitchingTable());
                        break;
                }
            }
            else
            {
                throw new Exception("Unhandled conf query");
            }
        }


        /// <summary>
        /// responsible for synchronization
        /// </summary>
        private void HandleClockTick()
        {
            //handle data received on Multiplexers
            foreach (var multiplexer in _multiplexers.Values)
            {
                var vc4 = multiplexer.Demultiplex();
                if (vc4 == null) continue;
                var egressPortId = multiplexer.EgressLogicalPortId;
                var stm1 = new Stm1 { Vc4 = vc4 };
                OnStm1PacketReceived(stm1, egressPortId);
            }
            //send buffered stms1
            foreach (var stm1DicRecord in _egressStm1Dictionary)
            {

                var egressPortId = stm1DicRecord.Key;
                var stm1 = stm1DicRecord.Value;

                Multiplexer multiplexer;
                if (_multiplexers.TryGetValue(egressPortId, out multiplexer))
                {
                    //when logical port is connected to a multiplexer
                    multiplexer.MultiplexAndSend(stm1.Vc4);
                }
                else
                {
                    //otherwise
                    _physicalPorts[egressPortId].Send(stm1);
                }

            }
            //reset buffer
            _egressStm1Dictionary.Clear();
        }

        /// <summary>
        /// handles received stm1 packets
        /// </summary>
        /// <param name="stm1Packet">received stm1</param>
        /// <param name="ingressPortId">port id on which the packet is received</param>
        void OnStm1PacketReceived(Stm1 stm1Packet, int ingressPortId)
        {
            var vc4 = stm1Packet.Vc4;

            //ingress vc4 composed of vcs
            if (vc4.IsComposite())
            {
                var vcs = vc4.Vcs;
                //setup egress stm1s
                foreach (var vcId in vcs.Keys)
                {
                    var switchingInfo = _switchingTable.Lookup(new SwitchingInfo(ingressPortId, vcId));
                    if (switchingInfo != null)
                    {
                        var vc = vcs[vcId];
                        var egressPortId = switchingInfo.PortId;
                        var egressVcId = switchingInfo.VcId;
                        Stm1 egressStm1;
                        if (!_egressStm1Dictionary.TryGetValue(egressPortId, out egressStm1))
                        {
                            egressStm1 =
                                new Stm1()
                                {
                                    Vc4 = new Vc4() { Poh = Utils.GeneratePoh() },
                                    Msoh = Utils.GenerateMsoh(_id),
                                    Rsoh = Utils.GenerateRsoh(_id)
                                };
                            _egressStm1Dictionary[egressPortId] = egressStm1;
                        }
                        egressStm1.Vc4.SetVirtualContainer(vc, egressVcId); //todo: what if already set
                    }
                }
            }
            //vc4 isnt composed of vcs
            else
            {
                var egressStm1 = new Stm1
                {
                    Vc4 = vc4,
                    Msoh = Utils.GenerateMsoh(_id),
                    Rsoh = Utils.GenerateRsoh(_id)
                };

                var egressSwitchingInfo = _switchingTable.Lookup(new SwitchingInfo(ingressPortId, 0));
                if (egressSwitchingInfo != null)
                {
                    var egressPortId = egressSwitchingInfo.PortId;
                    //place stm1 in egressbuffor
                    if (_egressStm1Dictionary.ContainsKey(egressPortId))
                    {
                        throw new Exception("Synch problem");
                    }
                    _egressStm1Dictionary[egressPortId] = egressStm1;
                }
            }
        }
    }
}
